Skip to content

[Insights] Stop plotting future periods and future-dated expenses on Spend, also don't show months with no data on Compliance - #106514

Open
OlGierd03 wants to merge 11 commits into
Expensify:mainfrom
software-mansion-labs:@OlGierd03/Insights-Plotted-Periods
Open

OlGierd03 wants to merge 11 commits into
Expensify:mainfrom
software-mansion-labs:@OlGierd03/Insights-Plotted-Periods

Conversation

@OlGierd03

@OlGierd03 OlGierd03 commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

Stops Spend over time at today. The current line ends at the period that contains today. With Compare: Previous period the compared line runs through the whole date range, which drops the rule from #105150 that ended the chart at the compared period's last expense. This month offers only Day, This quarter only Week and This year Week and Month, since other groupings would plot one or two mostly partial points. GetInsights leaves out expenses dated after asOfDate and returns them in futureExpenses, shown as Not shown: $X in future expenses. View. The charts, CSV export and Details panel take today from asOfDate (getInsightsLastDay), so they stop on the same day as the data. Home is unchanged. On Compliance, Violations over time leaves out buckets before Jun 1, 2026 (CONST.INSIGHTS.VIOLATIONS_TRACKED_FROM) and shows Violations are tracked from Jun 1, 2026. when the range or compared period starts earlier, as agreed in #105266. Date range and note code is extracted into getInsightsDateRange and InsightsChartNote, with no change in behavior.

Note

Both notes sit below the chart, matching the design mock in the issue. The issue text says "below its header and above the plot".

Note

The Insights widget on Home gets these changes in a follow-up.

Warning

The Compare: Average line on Violations over time is computed over the whole date range, including the months before Jun 1, 2026.

The average comes from GetInsights, which counts the months with no recorded violations as zero, so for This year it reads lower than the tracked months alone. Needs a backend change.

Fixed Issues

$ #105790
$ #102809

Tests

Precondition: account with the insightsCompliance beta that is an admin of a Control workspace. The workspace has violations recorded after Jun 1, 2026 and expenses in the current month, some of them dated after today.

  1. Open Insights > Spend, set Date to This month and Group by to Day. Verify Spend over time ends at today and draws nothing, not even $0, for later days.
  2. Verify Not shown: <amount> in future expenses. View shows below the chart. Press View and verify Spend opens with expenses dated after today through the end of the month, in the dashboard's workspaces.
  3. Set Compare to Previous period and verify the previous month's line runs through the whole month while the current line stops at today.
  4. Open Group by and verify This month offers only Day, This quarter only Week and This year Week and Month. Verify Last month still offers Week.
  5. With Week selected on Last month, switch Date to This month and verify the pill shows Day.
  6. Open the details of today's point and verify Top expenses leaves out expenses dated after today.
  7. Download the Spend over time CSV and verify it has no rows after today.
  8. Switch to Compliance, set Date to This year and Group by to Month. Verify Violations over time starts at June and Violations are tracked from Jun 1, 2026. shows below the chart.
  9. Set Compare to Previous period and verify no compared line is drawn and the note still shows.
  10. Set Date to Last 30 days with Compare off and verify the note is hidden.
  11. Repeat steps 2 and 8 on a narrow layout and verify the notes wrap.

Offline tests

  1. Open Insights > Spend with This month, then go offline.
  2. Verify Spend over time keeps the loaded data and the future expenses note.
  3. Switch to Compliance and verify Violations over time keeps its data and the tracking note.

QA Steps

Same as tests

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified any copy / text that was added to the app is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If the PR adds or modifies the UI:
    • I asked an AI agent to review the changes for accessibility issues and addressed its findings.
    • I tested with a screen reader (VoiceOver on macOS) and verified all new/changed elements are reachable with a logical focus order.
    • I verified all new/changed elements have meaningful accessible names and roles.
    • I verified state changes are announced (e.g. checked/unchecked, expanded/collapsed, selected).
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2026-11-12.at.19.40.31.mov

@melvin-bot

melvin-bot Bot commented Nov 10, 2026

Copy link
Copy Markdown

Hey, I noticed you changed src/languages/en.ts in a PR from a fork. For security reasons, translations are not generated automatically for PRs from forks.

If you want to automatically generate translations for other locales, an Expensify employee will have to:

  1. Look at the code and make sure there are no malicious changes.
  2. Run the Generate static translations GitHub workflow. If you have write access and the K2 extension, you can simply click: [this button]

Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running:

npx bun ./scripts/generateTranslations.ts --help

Typically, you'd want to translate only what you changed by running npx bun ./scripts/generateTranslations.ts --compare-ref main

@OSBotify

Copy link
Copy Markdown
Contributor

🦜 Polyglot Parrot! 🦜

Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues:

View the translation diff
diff --git a/src/languages/de.ts b/src/languages/de.ts
index d4075d6b..98277c64 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1258,6 +1258,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'Alle anzeigen',
         topExpenses: 'Top-Ausgaben',
         noExpensesInRange: 'Keine Ausgaben in diesem Bereich',
+        futureExpensesNotShown: (amount: string) => `Nicht angezeigt: ${amount} in zukünftigen Ausgaben.`,
+        violationsTrackedFrom: (date: string) => `Verstöße werden ab dem ${date} erfasst.`,
     },
     allSettingsScreen: {
         subscription: 'Abonnement',
diff --git a/src/languages/el.ts b/src/languages/el.ts
index ba409a10..43e0ed0b 100644
--- a/src/languages/el.ts
+++ b/src/languages/el.ts
@@ -1304,6 +1304,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'Προβολή όλων',
         topExpenses: 'Κορυφαίες δαπάνες',
         noExpensesInRange: 'Δεν υπάρχουν δαπάνες σε αυτό το εύρος',
+        futureExpensesNotShown: (amount: string) => `Δεν εμφανίζονται: ${amount} σε μελλοντικές δαπάνες.`,
+        violationsTrackedFrom: (date: string) => `Οι παραβάσεις παρακολουθούνται από ${date}.`,
     },
     allSettingsScreen: {
         subscription: 'Συνδρομή',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 37fdf70e..a52daa93 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1252,6 +1252,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'Ver todo',
         topExpenses: 'Gastos principales',
         noExpensesInRange: 'No hay gastos en este intervalo',
+        futureExpensesNotShown: (amount: string) => `No se muestran: ${amount} en gastos futuros.`,
+        violationsTrackedFrom: (date: string) => `Las infracciones se registran desde el ${date}.`,
     },
     allSettingsScreen: {
         subscription: 'Suscripcion',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index d487a103..54ef0511 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1265,6 +1265,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'Tout afficher',
         topExpenses: 'Dépenses principales',
         noExpensesInRange: 'Aucune dépense dans cette plage',
+        futureExpensesNotShown: (amount: string) => `Non affiché : ${amount} en dépenses futures.`,
+        violationsTrackedFrom: (date: string) => `Les violations sont suivies à partir du ${date}.`,
     },
     allSettingsScreen: {
         subscription: 'Abonnement',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 79c3e93c..ae61a06a 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1261,6 +1261,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'Vedi tutto',
         topExpenses: 'Spese principali',
         noExpensesInRange: 'Nessuna spesa in questo intervallo',
+        futureExpensesNotShown: (amount: string) => `Non mostrato: ${amount} in spese future.`,
+        violationsTrackedFrom: (date: string) => `Le violazioni vengono tracciate a partire dal ${date}.`,
     },
     allSettingsScreen: {
         subscription: 'Abbonamento',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 2eaad98e..3310d11d 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1246,6 +1246,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'すべて表示',
         topExpenses: '上位の経費',
         noExpensesInRange: 'この範囲内に経費はありません',
+        futureExpensesNotShown: (amount: string) => `非表示:将来の経費に${amount}あります。`,
+        violationsTrackedFrom: (date: string) => `違反は${date}から追跡されます。`,
     },
     allSettingsScreen: {
         subscription: 'サブスクリプション',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index fc4e7c40..a41fa1f9 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1257,6 +1257,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'Alles bekijken',
         topExpenses: 'Belangrijkste uitgaven',
         noExpensesInRange: 'Geen uitgaven in dit bereik',
+        futureExpensesNotShown: (amount: string) => `Niet weergegeven: ${amount} aan toekomstige uitgaven.`,
+        violationsTrackedFrom: (date: string) => `Overtredingen worden bijgehouden vanaf ${date}.`,
     },
     allSettingsScreen: {
         subscription: 'Abonnement',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index df6e36b5..257810e0 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1284,6 +1284,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'Zobacz wszystko',
         topExpenses: 'Najwyższe wydatki',
         noExpensesInRange: 'Brak wydatków w tym zakresie',
+        futureExpensesNotShown: (amount: string) => `Nie pokazano: ${amount} w przyszłych wydatkach.`,
+        violationsTrackedFrom: (date: string) => `Naruszenia są śledzone od ${date}.`,
     },
     allSettingsScreen: {
         subscription: 'Subskrypcja',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index eb282b53..a967c815 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1260,6 +1260,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: 'Ver tudo',
         topExpenses: 'Maiores despesas',
         noExpensesInRange: 'Nenhuma despesa nesse intervalo',
+        futureExpensesNotShown: (amount: string) => `Não mostrado: ${amount} em despesas futuras.`,
+        violationsTrackedFrom: (date: string) => `Violações são registradas a partir de ${date}.`,
     },
     allSettingsScreen: {
         subscription: 'Assinatura',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 981594ba..07d665fa 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1207,6 +1207,8 @@ const translations: TranslationDeepObject<typeof en> = {
         viewAll: '查看全部',
         topExpenses: '最高花费',
         noExpensesInRange: '此范围内没有报销费用',
+        futureExpensesNotShown: (amount: string) => `未显示:未来报销金额 ${amount}。`,
+        violationsTrackedFrom: (date: string) => `从 ${date} 起开始记录违规情况。`,
     },
     allSettingsScreen: {
         subscription: '订阅',

Note

You can apply these changes to your branch by copying the patch to your clipboard, then running pbpaste | git apply 😉

View workflow run

@github-actions

Copy link
Copy Markdown
Contributor

🚧 JmillsExpensify has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

Copy link
Copy Markdown
Contributor

🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
Built from App PR #106514 with the pinned Mobile-Expensify submodule.

Android 🤖 iOS 🍎
⏩ SKIPPED ⏩ ⏩ SKIPPED ⏩
The build for Android was skipped The build for iOS was skipped
Web 🕸️
https://106514.pr-testing.expensify.com
Web

👀 View the workflow run that generated this build 👀

@JmillsExpensify

Copy link
Copy Markdown
Contributor

I just took the adhoc for a spin. Looking great so far, though the future-dated expenses notice didn't seem to work for me. The total it showed on the Insight didn't match the number that I saw on the Spend page.

@codecov

codecov Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
src/CONST/index.ts 96.09% <ø> (+0.06%) ⬆️
src/ROUTES.ts 34.33% <100.00%> (+0.14%) ⬆️
src/components/Search/buildChartSeries.ts 100.00% <100.00%> (ø)
src/pages/Insights/dashboardSpecs.ts 100.00% <ø> (ø)
src/pages/Insights/getInsightsDateRange.ts 100.00% <100.00%> (ø)
src/pages/Insights/getInsightsLastDay.ts 100.00% <100.00%> (ø)
src/pages/Insights/insightsCompare.ts 92.30% <100.00%> (-1.45%) ⬇️
src/pages/Insights/insightsGroupByUtils.ts 97.91% <100.00%> (+5.60%) ⬆️
src/pages/Insights/insightsQueries.ts 94.73% <100.00%> (+1.29%) ⬆️
src/pages/Insights/insightsTrackedFrom.ts 100.00% <100.00%> (ø)
... and 8 more
... and 124 files with indirect coverage changes

@OlGierd03
OlGierd03 marked this pull request as ready for review November 12, 2026 20:26
@OlGierd03
OlGierd03 requested review from a team as code owners November 12, 2026 20:26
@melvin-bot
melvin-bot Bot requested review from daledah and truph01 and removed request for a team November 12, 2026 20:26
@melvin-bot

melvin-bot Bot commented Nov 12, 2026

Copy link
Copy Markdown

@truph01 @daledah One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot requested review from JmillsExpensify and removed request for a team November 12, 2026 20:26
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-12T20:45:10.736927Z ed41480 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@OlGierd03

OlGierd03 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor Author

@JmillsExpensify I pushed a few changes and I think everything should line up now, with two exceptions. The Compliance average isn't covered yet and the Home widget will get these changes in a follow-up, since it needs proper syncing between the Search and GetInsights queries and that needs a bit more thought (if that's okay, the mismatch on the Home widget will stay the same as on production for now)

@OlGierd03

Copy link
Copy Markdown
Contributor Author

@MelvinBot withdraw the review request from @daledah

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed4148065c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (!from && !to) {
return {};
}
return {[FILTER_KEYS.DATE_RANGE]: getRangeQueryValue(from, to)};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retain the untouched side of custom date ranges

When a custom range needs only an upper cap (for example Oct 1–31 with asOfDate Oct 8), buildFilterFormValues has already supplied one DATE_RANGE, but this return overwrites it with date<=2026-10-08 because from is undefined; View on Spend and ranking-chart Details then include every older expense. The symmetric trackedFrom-only case drops the original end too, so construct the replacement from the later original/tracking start and the earlier original/last day rather than omitting unchanged bounds.

Useful? React with 👍 / 👎.

/>
asOfDate={asOfDate}
>
{!!asOfDate && !!futureExpenses?.count && (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep exclusion notes reachable for empty dashboards

When the selected Spend range contains only expenses after asOfDate, all graph snapshots have no counted rows, so getDashboardState resolves EMPTY/NO_EXPENSES and the stand-in branch returns before this InsightsChartWidget and its note child are mounted. The same happens for a Compliance range wholly before trackedFrom, leaving users with a generic empty state and no View link or tracking-start explanation; render these notes in the stand-in path or avoid the stand-in when excluded data explains the empty result.

Useful? React with 👍 / 👎.

@MelvinBot
MelvinBot removed the request for review from daledah November 12, 2026 20:46
@MelvinBot

Copy link
Copy Markdown
Contributor

Withdrew the review request from daledah.


view run

@OlGierd03

Copy link
Copy Markdown
Contributor Author

@daledah I removed your review request here, sorry for the noise 🙏 This PR is mainly tied to #105790, which @truph01 is assigned to. It also fixes the thing we discussed in the PR for #102809 (that's why Melvin asked you for a review), but yeah, the main issue is #105790

{...buildFilterFormValues(filters, true, chart.dateFilterKey), has: chart.has, groupBy: chart.groupBy ?? getQueryGroupBy(filters), view: chart.view, compare},
{
...buildFilterFormValues(filters, true, chart.dateFilterKey),
...(!!lastDay && isExpenseDateChart && buildDataDaysFormValues(filters.date, lastDay, chart.trackedFrom)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a stored range of Oct 1 – Dec 31 (today Nov 13), "View on Spend" opens with just date<=2026-11-13, so the list goes back to Dec 2024:

Screen.Recording.2026-11-13.at.15.07.51.mov

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the stopping-at-today tests only use presets. Maybe add a custom range that runs past today, and one that starts before Jun 1? Either one would've caught the range losing an end.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants